﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO;
using System.Reflection;
using System.Globalization;
using System.ComponentModel;

using System.Reflection.Emit;

using Optimization.Utilities;

using System.Xml;
using System.Xml.Serialization;

namespace Optimization.Experiment
{
    /*
    [Serializable]
    public class ExperimentDescriptor2
    {
        [XmlElement("Problem")]
        public ProblemDescriptor Problem;
        [XmlElement("Solver")]
        public SolverDescriptor Solver;
        [XmlElement("Solutions")]
        public Solution[] Solutions;

        [XmlAttribute("Seed")]
        public int Seed;
    }
    */
    [Serializable]
    public struct ProblemDescriptor
    {
        private string name;
        private void SetName(string value)
        {
            this.name = value;
            Type problemType = Type.GetType(this.name);
            if (problemType == null)
            {
                Console.WriteLine("Problem type " + value + " not found");
                return;
            }
            //IProblem p = Activator.CreateInstance(problemType) as IProblem;
            if (Properties == null)
                Properties = PropertiesToArray(value);
        }

        [XmlAttribute("Name")]
        public string Name
        {
            get { return name; }
            set { SetName(value); }
        }

        //public struct P: PropertyX

        [XmlArrayItem("p", typeof(PropertyXml))]
        [XmlArray("ps")]
        public PropertyXml[] Properties;

        public string AsString()
        {
            if (Properties != null)
                return Name + Properties.Aggregate("", (s, p) => (s + p.Name + "=" + p.Value + ";"));
            else
                return Name;
        }

        internal ProblemDescriptor Copy()
        {
            ProblemDescriptor result = new ProblemDescriptor();
            result.Name = this.Name;
            if (this.Properties != null)
                result.Properties = this.Properties.Select((p) => new PropertyXml(p.Name, p.Value)).ToArray();
            return result;
        }

        static internal void ApplyProperties(IProblem problem, PropertyXml[] properties)
        {
            if (properties == null)
                return;

            Type problemType = problem.GetType();
            //init properties
            foreach (PropertyXml cProperty in properties)
            {
                string pName = cProperty.Name;
                string pValue = cProperty.Value;

                PropertyInfo pi = problemType.GetProperty(pName);
                Type propType = pi.PropertyType;
                if (propType == typeof(string))
                    pi.GetSetMethod().Invoke(problem, new object[] { pValue });
                else if (propType == typeof(float))
                    pi.GetSetMethod().Invoke(problem, new object[] { float.Parse(pValue, CultureInfo.InvariantCulture) });
                else if (propType == typeof(double))
                    pi.GetSetMethod().Invoke(problem, new object[] { double.Parse(pValue, CultureInfo.InvariantCulture) });
                else if (propType == typeof(int))
                    pi.GetSetMethod().Invoke(problem, new object[] { int.Parse(pValue, CultureInfo.InvariantCulture) });
                else
                    pi.GetSetMethod().Invoke(problem, new object[] { pValue });
            }
        }

        internal Func<IProblem> CreateProblemConstructor()
        {
            Type problemType = Type.GetType(this.Name);
            PropertyXml[] localProps = Properties;
            return () =>
                {
                    IProblem problemResult = Activator.CreateInstance(problemType) as IProblem;
                    if (localProps != null)
                        ApplyProperties(problemResult, localProps);
                    return problemResult;
                };
        }

        internal PropertyXml[] PropertiesToArray(string name)
        {
            Type problemType = Type.GetType(name);
            if (problemType == null)
                return null;
            IProblem problem = Activator.CreateInstance(problemType) as IProblem;
            //Properties 
            List<PropertyXml> result = new List<PropertyXml>();

            //Type solverType = Type.GetType(this.Solver.Name);
            string problemTypeName = problemType.Name;

            PropertyInfo[] pi = problemType.GetProperties().
                Where(p => p.GetCustomAttributes(typeof(CategoryAttribute), false).Any()).
                OrderBy(p => p.PropertyType.AssemblyQualifiedName).ToArray();

            foreach (PropertyInfo p in pi)
            {
                MethodInfo miGet = p.GetGetMethod();
                var propVal = miGet.Invoke(problem, null);
                string propName = p.Name;
                Type propType = p.PropertyType;
                string propValT = "";
                if (propType == typeof(string))
                    propValT = "\"" + propVal.ToString() + "\"";
                else if (propType == typeof(float))
                {
                    float fVal = (float)propVal;
                    propValT = fVal.ToString(CultureInfo.InvariantCulture);
                }
                else if (propType == typeof(double))
                {
                    double dVal = (double)propVal;
                    propValT = dVal.ToString(CultureInfo.InvariantCulture);
                }
                else
                    propValT = propVal.ToString();

                result.Add(new PropertyXml(propName, propValT));

            }

            return result.ToArray();
        }
    }

    [Serializable]
    public struct SolverDescriptor
    {
        private string name;
        private void SetName(string value)
        {
            this.name = value;
            if (Properties == null)
                Properties = PropertiesToArray(name);
        }


        internal PropertyXml[] PropertiesToArray(string name)
        {
            Type solverType = Type.GetType(name);
            if (solverType == null)
                return null;
            ISolver solver = Activator.CreateInstance(solverType) as ISolver;
            //Properties 
            List<PropertyXml> result = new List<PropertyXml>();

            //Type solverType = Type.GetType(this.Solver.Name);
            string solverTypeName = solverType.Name;

            PropertyInfo[] pi = solverType.GetProperties().
                Where(p => p.GetCustomAttributes(typeof(CategoryAttribute), false).Any()).
                OrderBy(p => p.PropertyType.AssemblyQualifiedName).ToArray();

            foreach (PropertyInfo p in pi)
            {
                MethodInfo miGet = p.GetGetMethod();
                var propVal = miGet.Invoke(solver, null);
                string propName = p.Name;
                Type propType = p.PropertyType;
                string propValT = "";
                if (propType == typeof(string))
                    propValT = "\"" + propVal.ToString() + "\"";
                else if (propType == typeof(float))
                {
                    float fVal = (float)propVal;
                    propValT = fVal.ToString(CultureInfo.InvariantCulture);
                }
                else if (propType == typeof(double))
                {
                    double dVal = (double)propVal;
                    propValT = dVal.ToString(CultureInfo.InvariantCulture);
                }
                else
                    propValT = propVal.ToString();

                result.Add(new PropertyXml(propName, propValT));

            }

            return result.ToArray();
        }

        [XmlAttribute("Name")]
        public string Name
        {
            get { return name; }
            set { SetName(value); }
        }

        [XmlArrayItem("p", typeof(PropertyXml))]
        [XmlArray("ps")]
        public PropertyXml[] Properties;

        public string AsString()
        {
            if (Properties != null)
                return Name + Properties.Aggregate("", (s, p) => (s + p.Name + "=" + p.Value + ";"));
            else
                return Name;
        }

        internal SolverDescriptor Copy()
        {
            SolverDescriptor result = new SolverDescriptor();
            result.Name = this.Name;
            if (this.Properties != null)
                result.Properties = this.Properties.Select((p) => (new PropertyXml(p.Name, p.Value))).ToArray();
            return result;
        }

        static internal void ApplyProperties(object solver, PropertyXml[] Properties)
        {
            if (Properties == null)
                return;

            Type solverType = solver.GetType();
            //init properties
            foreach (PropertyXml cProperty in Properties)
            {
                string pName = cProperty.Name;
                string pValue = cProperty.Value;

                PropertyInfo pi = solverType.GetProperty(pName);
                Type propType = pi.PropertyType;
                if (propType == typeof(string))
                    pi.GetSetMethod().Invoke(solver, new object[] { pValue });
                else if (propType == typeof(float))
                    pi.GetSetMethod().Invoke(solver, new object[] { float.Parse(pValue, CultureInfo.InvariantCulture) });
                else if (propType == typeof(double))
                    pi.GetSetMethod().Invoke(solver, new object[] { double.Parse(pValue, CultureInfo.InvariantCulture) });
                else if (propType == typeof(int))
                    pi.GetSetMethod().Invoke(solver, new object[] { int.Parse(pValue, CultureInfo.InvariantCulture) });
                else
                    pi.GetSetMethod().Invoke(solver, new object[] { pValue });
            }
        }

        internal Func<ISolver> CreateConstructor()
        {
            Func<ISolver> result;
            /*
            ConstructorInfo target = type.GetConstructor(Type.EmptyTypes);

            DynamicMethod dynamic = new DynamicMethod(string.Empty,
                       typeof(object),
                       new Type[0],
                       target.DeclaringType);
            ILGenerator il = dynamic.GetILGenerator();
            il.DeclareLocal(target.DeclaringType);
            il.Emit(OpCodes.Newobj, target);
            il.Emit(OpCodes.Stloc_0);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ret);

            //Error binding to target method.
            result = (Func<ISolver>)dynamic.CreateDelegate(typeof(Func<ISolver>));
            */
            Type solverType = Type.GetType(this.name);
            PropertyXml[] localProps = this.Properties;
            result = () =>
                {
                    ISolver solverResult = Activator.CreateInstance(solverType) as ISolver;
                    if (localProps != null)
                        ApplyProperties(solverResult, localProps);
                    return solverResult;
                };
            return result;
        }
    }

    [Serializable]
    public struct Solution
    {
        [XmlAttribute("Step")]
        public int Step;

        [XmlAttribute("Value")]
        public double ResultValue;

        [XmlArrayItem("d", typeof(double))]
        [XmlArray("pos")]
        public double[] Result;

        [XmlArrayItem("p", typeof(ParticleXml))]
        [XmlArray("ps")]
        public ParticleXml[] Particles;

        internal Solution Copy()
        {
            Solution result = new Solution();

            result.Step = this.Step;
            result.ResultValue = this.ResultValue;
            if (this.Result != null)
                result.Result = this.Result.Select((d) => (d)).ToArray();
            if (this.Particles != null)
                result.Particles = this.Particles.Select((p) => (p.Copy())).ToArray();

            return result;
        }
    }

    [Serializable]
    public struct PropertyXml
    {
        [XmlAttribute("Name")]
        public string Name;
        [XmlAttribute("Value")]
        public string Value;

        public PropertyXml(string propName, string propValT)
        {
            this.Name = propName;
            this.Value = propValT;
        }


    }


    [Serializable]
    public struct ParticleXml
    {
        [XmlAttribute("Value")]
        public double Value;

        [XmlArrayItem("d", typeof(double))]
        [XmlArray("pos")]
        public double[] Position;

        internal ParticleXml Copy()
        {
            ParticleXml result = new ParticleXml();

            result.Value = this.Value;
            result.Position = this.Position.Select((d) => (d)).ToArray();


            return result;
        }

        public ParticleXml(double Value, double[] Position)
        {
            this.Value = Value;
            this.Position = Position.Select((d) => d).ToArray();
        }
    }


    [Serializable]
    public class ParticleSnapShot
    {
        public ParticleSnapShot(Vector Position, double Value)
        {
            this.Position = Position.Copy();
            this.Value = Value;
        }

        public ParticleSnapShot()
        {
        }

        public Vector Position;
        public double Value;
    }

    [Serializable]
    public class ExperimentDescriptor
    {
        [XmlElement("Problem")]
        public ProblemDescriptor Problem;
        //public string ProblemName;
        [XmlElement("Solver")]
        public SolverDescriptor Solver;
        //public string SolverName;
        [XmlElement("Seed")]
        public int Seed = -1;
        //public string[] Properties;

        //public int Steps;
        public IEnumerable<int> GetSteps()
        {
            return Solutions.GroupBy((s) => s.Step).Select(gr => gr.Key);
        }

        [XmlArrayItem("s", typeof(Solution))]
        [XmlArray("Ss")]
        public Solution[] Solutions;


        //public double SolutionValue = double.MaxValue;
        //public string SolutionVector = "";

        //public int LastChange = -1;

        static public string Test = "SimIdent.DUB_MA_LIN, SimIdent, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\t" +
            "Optimization.StandardSolver.PSO, StandardSolvers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\t" +
            "C0=1;C1=1;C2=1;SwarmSize=8\t" +
            "15648164\t" +
            "25";

        static public bool SaveSnapshot = true;
        /* SimIdent.DUB_MA_LIN, SimIdent, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null	
         * Optimization.StandardSolver.PSO, StandardSolvers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null	
         * SwarmSize=8;C0=1f;C1=1f;C2=1f;C3=0.5f;StepSize=0.1f	
         * 15648164	
         * 25	
         * 6096,91095175535	
         * [108,208997728429 0,065870009707905 0,0286895262313972]d
         */

        #region ExtendedLog
        public ParticleSnapShot[] Snapshots = null;
        #endregion

        public ExperimentDescriptor Copy()
        {
            ExperimentDescriptor result = new ExperimentDescriptor();
            result.Problem = this.Problem.Copy();
            result.Solver = this.Solver.Copy();
            result.Seed = this.Seed;

            if (this.Solutions != null)
                result.Solutions = this.Solutions.Select((s) => (s.Copy())).ToArray();
            //result.Steps = Steps;
            //result.SolutionValue = SolutionValue;
            //result.SolutionVector = SolutionVector;
            //result.LastChange = LastChange;

            if (Snapshots != null)
                result.Snapshots = Snapshots.Select(s => new ParticleSnapShot(s.Position, s.Value)).ToArray();

            return result;
        }

        public static Func<IProblem> CreateProblemConstructor(Type type)
        {
            Func<IProblem> result;
            /*
            ConstructorInfo target = type.GetConstructor(Type.EmptyTypes);

            DynamicMethod dynamic = new DynamicMethod(string.Empty,
                       typeof(object),
                       new Type[0],
                       target.DeclaringType);
            ILGenerator il = dynamic.GetILGenerator();
            il.DeclareLocal(target.DeclaringType);
            il.Emit(OpCodes.Newobj, target);
            il.Emit(OpCodes.Stloc_0);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ret);
            
            result = (Func<IProblem>)dynamic.CreateDelegate(typeof(Func<IProblem>));
            */
            result = () => Activator.CreateInstance(type) as IProblem;
            return result;

        }

        public static Func<ISolver> CreateSolverConstructor(Type type)
        {
            Func<ISolver> result;
            /*
            ConstructorInfo target = type.GetConstructor(Type.EmptyTypes);

            DynamicMethod dynamic = new DynamicMethod(string.Empty,
                       typeof(object),
                       new Type[0],
                       target.DeclaringType);
            ILGenerator il = dynamic.GetILGenerator();
            il.DeclareLocal(target.DeclaringType);
            il.Emit(OpCodes.Newobj, target);
            il.Emit(OpCodes.Stloc_0);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ret);

            //Error binding to target method.
            result = (Func<ISolver>)dynamic.CreateDelegate(typeof(Func<ISolver>));
            */
            result = () => Activator.CreateInstance(type) as ISolver;
            return result;

        }

        public static ExperimentDescriptor Parse(string Value)
        {
            ExperimentDescriptor result = new ExperimentDescriptor();

            string[] pars = Value.Split(new char[] { '\t' });

            result.Problem.Name = pars[0];
            result.Solver.Name = pars[1];
            //result.Properties = pars[2].Split(new char[] { ';' });
            result.Seed = int.Parse(pars[3].Replace(" ", ""));
            //result.Steps = int.Parse(pars[4].Replace(" ", ""));
            /*
            if (pars.Length > 5)
                result.SolutionValue = double.Parse(pars[5]);

            if (pars.Length > 6)
                result.SolutionVector = pars[6];

            if (pars.Length > 7)
                result.LastChange = int.Parse(pars[7]);
            */
            return result;
        }

        public static ExperimentDescriptor[] ParseXML(string XMLstring)
        {
            return null;
        }

        internal IProblem CreateProblem()
        {
            return CreateProblemConstructor()();
        }

        internal Func<IProblem> CreateProblemConstructor()
        {
            Type problemType = Type.GetType(this.Problem.Name);
            return CreateProblemConstructor(problemType);
        }

        internal ISolver CreateSolver()
        {
            return CreateSolverConstructor()();
        }

        private Func<ISolver> CreateSolverConstructor()
        {
            Type solverType = Type.GetType(this.Solver.Name);
            return CreateSolverConstructor(solverType);
        }

        public override string ToString()
        {
            string result = "";

            List<string> pars = new List<string>();
            pars.Add(Problem.Name);
            pars.Add(Solver.Name);
            string strProps = Solver.Properties.Aggregate("", (sum, next) => sum + next.Name + "=" + next.Value + ";");
            pars.Add(strProps);
            pars.Add(Seed.ToString());
            //pars.Add(Steps.ToString());
            //pars.Add(SolutionValue.ToString());
            //pars.Add(SolutionVector);
            //pars.Add(LastChange.ToString());

            result = pars.Aggregate((sum, next) => sum + '\t' + next);


            return result;
        }



        public static string ToString(ISolver solver, int Step)
        {
            string result;

            string problemName = "Unknown Problem";
            ParticleBasedMethod pbm = solver as ParticleBasedMethod;
            if (pbm != null)
            {
                if (pbm.Problem != null)
                    problemName = pbm.Problem.GetType().AssemblyQualifiedName;
            }
            result = problemName;
            result = result + "\t" + solver.GetType().AssemblyQualifiedName;

            Type solverType = solver.GetType();
            string solverTypeName = solverType.Name;

            PropertyInfo[] pi = solverType.GetProperties().
                Where(p => p.GetCustomAttributes(typeof(CategoryAttribute), false).Any()).
                OrderBy(p => p.PropertyType.AssemblyQualifiedName).ToArray();

            string props = "";// solverTypeName + " st = s as " + solverTypeName + ";\r\n";

            foreach (PropertyInfo p in pi)
            {
                MethodInfo miGet = p.GetGetMethod();
                var propVal = miGet.Invoke(solver, null);
                string propName = p.Name;
                Type propType = p.PropertyType;
                string propValT = "";
                if (propType == typeof(string))
                    propValT = "\"" + propVal.ToString() + "\"";
                else if (propType == typeof(float))
                {
                    float fVal = (float)propVal;
                    propValT = fVal.ToString(CultureInfo.InvariantCulture);
                }
                else if (propType == typeof(double))
                {
                    double dVal = (double)propVal;
                    propValT = dVal.ToString(CultureInfo.InvariantCulture);
                }
                else
                    propValT = propVal.ToString();
                if (props != "")
                    props = props + ";";
                props = props + propName + "=" + propValT;
            }

            result = result + "\t" + props;
            result = result + "\t" + Optimization.Utilities.Random.Seed.ToString();
            result = result + "\t" + Step.ToString();

            result = result + "\t" + solver.GetSolutionValue().ToString();
            result = result + "\t" + solver.GetSolution().ToString();
            return result;
        }
    }
}
